package dan.frontend
import chisel3._
import chisel3.util._
import dan.utility.FrontendUtility.fetchMask

class BPUIO extends Bundle with HasBPUParam{
    val s0Req = Flipped(Valid(new BPUReq()))
    val resp = Output(new Bundle {
        val s1 = new BPUBundle()
        val s2 = new BPUBundle()
        val s3 = new BPUBundle()
    })
    val updateInfo = Input(Valid(new PredictionUpdate()))
}

class BPU extends Module with HasBPUParam{
    val io = IO(new BPUIO())
    val s0Valid = io.s0Req.valid
    val s1Valid = RegNext(s0Valid)
    val s2Valid = RegNext(s1Valid)
    val s3Valid = RegNext(s2Valid)

    val s0Mask = fetchMask(io.s0Req.bits.pc)
    val s1Mask = RegNext(s0Mask)
    val s2Mask = RegNext(s1Mask)
    val s3Mask = RegNext(s2Mask)

    val s0PC = io.s0Req.bits.pc
    val s0HashedPC = mixHILO(s0PC)
    val s1PC = RegNext(s0PC)
    val s1HashedPC = RegNext(s0HashedPC)
    val s2PC = RegNext(s1PC)
    val s3PC = RegNext(s2PC)

    val s0Update = io.updateInfo
    val s1Update = RegNext(s0Update)

    val btb = Module(new BTB())
    val microBTB = Module(new MicroBTB())
    val lhp = Module(new LHP())
    val bim = Module(new BiM())

    btb.io.s1Update := s1Update
    btb.io.s0Valid := s0Valid
    btb.io.s0PC := s0PC
    btb.io.s0HashedPC := s0HashedPC

    microBTB.io.s1Update := s1Update
    microBTB.io.s1Valid := s1Valid
    microBTB.io.s1PC := s1PC
    microBTB.io.s1HashedPC := s1HashedPC

    bim.io.s1Update := s1Update
    bim.io.s0Valid := s0Valid
    bim.io.s0PC := s0PC

    lhp.io.s1Update := s1Update
    lhp.io.s0PC := s0PC
    // s1输出
    val s1JumpValid = WireInit(VecInit(Seq.fill(fetchWidth)(false.B)))
    io.resp.s1.bpuInfo := 0.U.asTypeOf(new BPUInfo())
    for(i <- (0 until fetchWidth).reverse){
        // 从fetchwidth遍历到0，低位是最优先执行的分支指令
        val taken = microBTB.io.s1IsBr(i) && microBTB.io.s1Taken(i) || microBTB.io.s1IsJal(i)
        s1JumpValid(i) := s1Valid && s1Mask(i) && microBTB.io.s1Target(i).valid && (taken)
        when(s1JumpValid(i)){
            io.resp.s1.bpuInfo.predictedTarget := microBTB.io.s1Target(i)
        }
    }
    io.resp.s1.bpuInfo.takens := s1JumpValid
    io.resp.s1.bpuInfo.targets := microBTB.io.s1Target
    io.resp.s1.pc := s1PC
    io.resp.s1.meta := DontCare
    // s2 输出
    io.resp.s2.bpuInfo := RegNext(io.resp.s1.bpuInfo)
    val s2JumpValid = WireInit(VecInit(Seq.fill(fetchWidth)(false.B)))
    for(i <- (0 until fetchWidth).reverse){
        val taken = btb.io.s2Target(i).valid && (btb.io.s2IsJal(i) || 
            // 实际上 btb.io.s2IsJal === btb.io.s2Taken === !btb.io.s2IsBr
            // btb.io.s2Taken(i) 实际没用
            ( btb.io.s2IsBr(i) && (btb.io.s2Taken(i) || bim.io.s2Taken(i)) )
        )
        s2JumpValid(i) := s2Mask(i) && s2Valid && taken
        when(s2JumpValid(i)){
            io.resp.s2.bpuInfo.predictedTarget := btb.io.s2Target(i)
        }
    }
    io.resp.s2.bpuInfo.takens := s2JumpValid
    io.resp.s2.bpuInfo.targets := btb.io.s2Target
    io.resp.s2.pc := s2PC
    io.resp.s2.meta := DontCare
    // s3输出
    io.resp.s3.bpuInfo := RegNext(io.resp.s2.bpuInfo)
    for(i <- 0 until fetchWidth){
        io.resp.s3.bpuInfo.takens(i) := RegNext(Mux(lhp.io.s2Taken(i).valid, lhp.io.s2Taken(i).bits, io.resp.s2.bpuInfo.takens(i)))
    }
    io.resp.s3.pc := s3PC
    for(i <- 0 until fetchWidth){
        io.resp.s3.meta(i).microBTBMeta := microBTB.io.s3Meta(i)
        io.resp.s3.meta(i).btbMeta := btb.io.s3Meta(i)
        io.resp.s3.meta(i).bimMeta := bim.io.s3Meta(i)
        io.resp.s3.meta(i).lhpMeta := lhp.io.s3Meta(i)
    }
}


